/**
 * Driver.java
 * 
 * Created by Chad Cross on 4/5/09
 * 
 * @author Chad Cross
 * @version 1.4
 * 
 * A simple test/driver class for inputting a regex
 * and then displaying an NFA and DFA
 * 
 * Adapted from Amer Gerzic's regex library: http://www.amergerzic.com/page/Free-Downloads.aspx
 * Licensed under the Code Project Open License: http://www.codeproject.com/info/cpol10.aspx
 * 
 */

package jmai.parser;

import java.io.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;

public class Driver {
	
	/** An adjacency matrix for the NFA.  See project description for structure */
	String[][] nfaAdjacencyMatrix;
	String[][] nfaAdjM;
	
	/** An adjacency matrix for the DFA.  See project description for structure */
	String[][] dfaAdjacencyMatrix;
	
	/** A new instance of the Parser class */
	Parser p;
	
	/** The regular expression the user keys in */
	static String input;
	
	/** A new instance of the ConvertNFA class */
	ConvertNFA c;
	
	String regex;
	
	public String[][] getDFA() {
		return dfaAdjacencyMatrix;
	}
	
	public String[][] getNFA() {
		return nfaAdjM;
	}

	public Driver(String input) {
		
		regex = input;
		
		//System.out.println(regex);
		
		/** Pass the user input to the parser to create an NFA representation */
		p = new Parser();
		ArrayList arrayNFA = p.convertToNFA(input);
					
		/** If the array is empty, don't show it */
		if (arrayNFA != null) {
			//System.out.println("Successfully converted the regex to an NFA");
			
			/** 
			 * Create the adjacency matrix to be n+1 * n+1 where n is the number
			 * of states in the array list representing the FA.
			 */
			nfaAdjacencyMatrix = new String [arrayNFA.size() + 1][(p.treeSetInput.size() + 2)];
		}//end if
		else {
			//System.out.println("Error: Unable to convert to an NFA");
		}//end else
		
		this.createNFAStruct(arrayNFA);

		c = new ConvertNFA();
		
		// JP		
		
		for(int i=1; i<nfaAdjacencyMatrix.length; ++i){
			// This is new state so add it to the
			String strStateName = (String)nfaAdjacencyMatrix[i][0];
			strStateName.trim();
			State state = new State(strStateName);
			if(i==1)
				c.setStartStateNFA(strStateName);
			
			// add state to the NFA structure
			c.addNFAState(state);
		}

		for(int i=1; i<=c.getNFAStates(); ++i){
			for(int j=1; j<nfaAdjacencyMatrix[0].length; ++j){

				// add all input characters to the converter
				if(i==1){
					String chrInputChar = (String)nfaAdjacencyMatrix[0][j];

					if(chrInputChar.length() == 0)
						break;
					
					c.addInputChar(chrInputChar);
				}
				
				String strTransitionEntry = (String)nfaAdjacencyMatrix[i][j];


				if(strTransitionEntry == null)
					continue;

				if(strTransitionEntry.length()==0)
					continue;
				
				String[] strStates = strTransitionEntry.split(",");

				Transition transition = new Transition((String)nfaAdjacencyMatrix[0][j]);

				for(int k=0; k<strStates.length; ++k){
					String strStateName = strStates[k];
					strStateName.trim();
					
					if(strStateName.length()==0)
						continue;

					State state = c.getStateNFA(strStateName);
					
					if(state != null){
						transition.stateArray.add(state);
					}else{
						
						// error
					}
					strStateName = (String)nfaAdjacencyMatrix[i][0];
					strStateName.trim();
					state = c.getStateNFA(strStateName);
					if(state != null){
						state.transitionsArray.add(transition);
						//State tmpState = (State)transition.stateArray.get(transition.stateArray.size()-1);
					}else{
						// error
					}
				}
			}
		}
		
		c.ConvertToDFA();
		
		String strDFAStartState = c.getStartStateDFA();
		
		TreeMap treeMapDFA = c.getDFATreeMap();
		
		createDFAStruct(strDFAStartState, treeMapDFA);
		
		
		
		
		//printDFAStruct();

		//
	}
	
	/**
	 * The default constructor
	 */
	@SuppressWarnings("unchecked")
	public Driver() {
		try {
			BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
			System.out.print("Input a RegEx:");
			input = new String(in.readLine());
			System.out.println();
		}//end try block
		catch (Exception e) {
			System.out.println("Exception: " + e.getMessage());	
		}//end catch block
	
		/** Pass the user input to the parser to create an NFA representation */
		p = new Parser();
		ArrayList arrayNFA = p.convertToNFA(input);
					
		/** If the array is empty, don't show it */
		if (arrayNFA != null) {
			System.out.println("Successfully converted the regex to an NFA");
			
			/** 
			 * Create the adjacency matrix to be n+1 * n+1 where n is the number
			 * of states in the array list representing the FA.
			 */
			nfaAdjacencyMatrix = new String [arrayNFA.size() + 1][(p.treeSetInput.size() + 2)];
		}//end if
		else {
			System.out.println("Error: Unable to convert to an NFA");
		}//end else
		
		this.createNFAStruct(arrayNFA);

		c = new ConvertNFA();
		
		// JP		
		
		for(int i=1; i<nfaAdjacencyMatrix.length; ++i){
			// This is new state so add it to the
			String strStateName = (String)nfaAdjacencyMatrix[i][0];
			strStateName.trim();
			State state = new State(strStateName);
			if(i==1)
				c.setStartStateNFA(strStateName);
			
			// add state to the NFA structure
			c.addNFAState(state);
		}

		for(int i=1; i<=c.getNFAStates(); ++i){
			for(int j=1; j<nfaAdjacencyMatrix[0].length; ++j){

				// add all input characters to the converter
				if(i==1){
					String chrInputChar = (String)nfaAdjacencyMatrix[0][j];

					if(chrInputChar.length() == 0)
						break;
					
					c.addInputChar(chrInputChar);
				}
				
				String strTransitionEntry = (String)nfaAdjacencyMatrix[i][j];


				if(strTransitionEntry == null)
					continue;

				if(strTransitionEntry.length()==0)
					continue;
				
				String[] strStates = strTransitionEntry.split(",");

				Transition transition = new Transition((String)nfaAdjacencyMatrix[0][j]);

				for(int k=0; k<strStates.length; ++k){
					String strStateName = strStates[k];
					strStateName.trim();
					
					if(strStateName.length()==0)
						continue;

					State state = c.getStateNFA(strStateName);
					
					if(state != null){
						transition.stateArray.add(state);
					}else{
						
						// error
					}
					strStateName = (String)nfaAdjacencyMatrix[i][0];
					strStateName.trim();
					state = c.getStateNFA(strStateName);
					if(state != null){
						state.transitionsArray.add(transition);
						//State tmpState = (State)transition.stateArray.get(transition.stateArray.size()-1);
					}else{
						// error
					}
				}
			}
		}
		
		c.ConvertToDFA();
		
		String strDFAStartState = c.getStartStateDFA();
		
		TreeMap treeMapDFA = c.getDFATreeMap();
		
		createDFAStruct(strDFAStartState, treeMapDFA);
		
		printNFAStruct();
		printDFAStruct();
	}
	
	public void createDFAStruct(String strDFAStartState, TreeMap treeMapDFA) {
		
		String[][] adjacencyMatrix = new String[treeMapDFA.size()+1][treeMapDFA.size()+1];
		
		dfaAdjacencyMatrix = new String[treeMapDFA.size()+1][c.inputArray.size()+1];
		
		int nColumn = 1;
		
		TreeMap charTreeMap = new TreeMap();
		
		for(int i=0; i<c.inputArray.size(); i++) {
			String strInChar = (String)c.inputArray.get(i);
			dfaAdjacencyMatrix[0][nColumn] = strInChar;
			charTreeMap.put(strInChar, new Integer(nColumn));
			nColumn++;
		}
		
		State DFAStartState = (State)treeMapDFA.get(strDFAStartState);
		
		treeMapDFA.remove(strDFAStartState);
		
		// JP
		Iterator iter = treeMapDFA.values().iterator();
		
		int row = 2;
		ArrayList<String> stateList = new ArrayList<String>(treeMapDFA.size()+1);
		stateList.add(DFAStartState.stateName);
		adjacencyMatrix[1][0] = DFAStartState.stateName;
		adjacencyMatrix[0][1] = DFAStartState.stateName;
		//System.out.println(DFAStartState.stateName);
		while(iter.hasNext()) {
			State tmpState = (State)iter.next();
			
			adjacencyMatrix[row][0] = tmpState.stateName;
			adjacencyMatrix[0][row] = tmpState.stateName;
			row++;
			stateList.add(tmpState.stateName);
		}
		//
		
		dfaAdjacencyMatrix[1][0] = DFAStartState.stateName;
		
		for(int i=0; i<DFAStartState.transitionsArray.size(); i++) {
			Transition t = (Transition) DFAStartState.transitionsArray.get(i);
			
			Integer nCol = (Integer)charTreeMap.get(t.transitionChar);
			
			dfaAdjacencyMatrix[1][nCol.intValue()] = ((State)t.stateArray.get(0)).stateName;
			
			// JP
			int z = stateList.indexOf(((State)t.stateArray.get(0)).stateName);
			adjacencyMatrix[1][z+1] = t.transitionChar;
			//
			
		}
		
		iter = treeMapDFA.values().iterator();
		
		int nRow = 2;

		while(iter.hasNext()){

			State tmpState = (State)iter.next();
			dfaAdjacencyMatrix[nRow][0] = tmpState.stateName;
			
			for(int j=0; j<tmpState.transitionsArray.size(); ++j){
				Transition t = (Transition)tmpState.transitionsArray.get(j);

				Integer nCol = (Integer)charTreeMap.get(t.transitionChar);
				dfaAdjacencyMatrix[nRow][nCol.intValue()] = ((State)t.stateArray.get(0)).stateName;
				
				// JP
				int z = stateList.indexOf(((State)t.stateArray.get(0)).stateName);
				adjacencyMatrix[nRow][z+1] = t.transitionChar;
				//
			}

			++nRow;
		}
		
		dfaAdjacencyMatrix = adjacencyMatrix;
		//printDFAStruct();
	}
	
	/**
	 * Create a new instance of the class
	 * @param args For coding standards only
	 */
	public static void main(String[] args) {		
		@SuppressWarnings("unused")
		Driver d = new Driver();
	}//end method main
	
	/**
	 * Given an array list, this method will convert the structure
	 * into an adjacency matrix
	 * @param NFA The ArrayList representing an NFA
	 */
	@SuppressWarnings("unchecked")
	public void createNFAStruct(ArrayList arrayListNFA) {
		TreeMap inputCharacters = new TreeMap();
		
		// JP
		String[][] adjacencyMatrix = new String[arrayListNFA.size()+1][arrayListNFA.size()+1];
		
		ArrayList<String> stateList = new ArrayList<String>(arrayListNFA.size());
		
		for(int i=1; i<=arrayListNFA.size(); i++) {
			State s = (State)arrayListNFA.get(i-1);
			adjacencyMatrix[0][i] = s.stateName;
			adjacencyMatrix[i][0] = s.stateName;
			
			stateList.add(s.stateName);
		}
		//
		
		Iterator anIterator = p.treeSetInput.iterator();
		
		int column = 1;
		
		/** Create a TreeMap of the the input */
		while (anIterator.hasNext()) {
			String inputChar = (String)anIterator.next();
			nfaAdjacencyMatrix[0][column] = inputChar;
			inputCharacters.put(inputChar, new Integer(column));
			++column;
		}//end while
		
		nfaAdjacencyMatrix[0][column] = "epsilon";
		inputCharacters.put("epsilon", new Integer(column));
		
		/** Create the NFA structure */
		
		/** Grab each state from the ArrayList and write its name to the columns and rows of the struct */
		for (int i = 0; i < arrayListNFA.size(); ++i) {
			State s = (State)arrayListNFA.get(i);
			//System.out.println("Got state " + s.stateName);
			nfaAdjacencyMatrix[i+1][0] = s.stateName;
			//nfaAdjacencyMatrix[i+1][0] = s.stateName;
			
			for (int j = 0; j < s.transitionsArray.size(); ++j) {
				//System.out.println("Transitions Array size: " + s.transitionsArray.size());
				Transition t = (Transition)s.transitionsArray.get(j);		
				Integer nColumn = (Integer)inputCharacters.get(t.transitionChar);
				String statesString = ((State)t.stateArray.get(0)).stateName;
				
				//JP
				int z = stateList.indexOf(((State)t.stateArray.get(0)).stateName);
				adjacencyMatrix[i+1][z+1] = t.transitionChar;
				//JP
				
				
				for (int k =1; k < t.stateArray.size(); ++k) {
					statesString += "," + ((State)t.stateArray.get(k)).stateName;
					//JP
					z = stateList.indexOf(((State)t.stateArray.get(0)).stateName);
					adjacencyMatrix[i+1][z+1] = t.transitionChar;
					//JP
				}
				String tableEntry = (String)nfaAdjacencyMatrix[i+1][nColumn.intValue()];
				
				if (tableEntry!= null)
					if (tableEntry.length() > 0)
						tableEntry += "," + statesString;
					else tableEntry = statesString;
				else tableEntry = statesString;

				nfaAdjacencyMatrix[i+1][nColumn] = tableEntry; //t.transitionChar;
				
				//System.out.println("State: " + s.stateName + " Transition: " + t.transitionChar);
			}
			
		}//end for loop
		
		nfaAdjM = adjacencyMatrix;
		//printNFAStruct();
		
	}//end method createNFAStruct
	
	
	/**
	 * Print out the NFA struct
	 */
	public void printNFAStruct() {
		System.out.println("-NFA--" + regex + "-");
		for (int row = 0; row < nfaAdjM.length; row++) {
			for (int col = 0; col < nfaAdjM[row].length; col++) {
				System.out.print(nfaAdjM[row][col] + "\t");
			}//end for loop
			System.out.println();
		}//end for loop
		System.out.println("-----------------------------------");
	}//end printNFAStruct
	
	/**
	 * Print out the DFA struct
	 */
	public void printDFAStruct() {
		System.out.println("-DFA--" + regex + "-");
		for (int row = 0; row < dfaAdjacencyMatrix.length; row++) {
			for (int col = 0; col < dfaAdjacencyMatrix[row].length; col++) {
				System.out.print(dfaAdjacencyMatrix[row][col] + "\t");
			}//end for loop
			System.out.println();
		}//end for loop
		System.out.println("-----------------------------------");
	}//end printNFAStruct
}//end class Driver
